apollo serverとclientでファイルをアップロードする方法について調査したのでまとめました。

こちらの公式のドキュメントと、こちらのyoutubeの動画に沿って実装しました。

実装環境は以下です。

$ node -v
v14.15.4
$ npm -v
7.6.3

ディレクトリ構成

.
├── client
│   ├── README.md
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   ├── manifest.json
│   │   └── robots.txt
│   ├── src
│   │   ├── App.js
│   │   ├── UploadForm.js
│   │   ├── index.css
│   │   └── index.js
│   └── yarn.lock
└── fileupload-server
    ├── package-lock.json
    ├── package.json
    ├── public
    │   └── images
    └── server.js

clientはreactです。 fileupload-server/public/images/にアップロードした画像が保存されます。

serverサイド

まずserver側から初期化を行なってから実装していきます。

$ mkdir fileupload-server
$ cd fileupload-server/
$ npm init -y
$ npm install apollo-server
$ 

server.js

const { ApolloServer, gql } = require('apollo-server');
const path = require('path')
const fs = require('fs')

const typeDefs = gql`
  type File {
    url: String!
  }

  type Query {
    hello: String!
  }

  type Mutation {
    uploadFile(file: Upload!): File!
  }
`;

const resolvers = {
  Query: {
    hello: () => 'Hello World',
  },
  Mutation: {
    uploadFile: async (parent, { file }) => {
      const { createReadStream, filename, mimetype, encording } = await file

      const stream = createReadStream()
      const pathName = path.join(__dirname, `/public/images/${filename}`)
      await stream.pipe(fs.createWriteStream(pathName))

      return {
        url: `http://localhost:4000/images/${filename}`
      }
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

front側

front側も初期化から

$ cd ..
$ npx create-react-app client --template clean-cra
$ npm install @apollo/client graphql apollo-upload-client

App.js

import React from 'react';
import { ApolloProvider, ApolloClient, InMemoryCache} from '@apollo/client'
import { createUploadLink} from 'apollo-upload-client'

import UploadForm from './UploadForm'

const clint = new ApolloClient({
  link: createUploadLink({
    uri: 'http://localhost:4000/'
  }),
  cache: new InMemoryCache(),
})

function App() {
  return (
    <ApolloProvider client={clint}>
      <UploadForm>

      </UploadForm>
    </ApolloProvider>
  );
}

export default App;

UploadForm.js

import React from 'react'
import { useMutation, gql  } from '@apollo/client'

const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`

export default function UploadForm() {
  const [uploadFile] = useMutation(UPLOAD_FILE, {
    onCompleted: data => console.log(data)
  })

  const handleFileChange = e => {
    const file = e.target.files[0]
    if (!file) return
    uploadFile({
      variables: {file}
    })
  }

  return (
    <div>
      <h1>Upload File</h1>
      <input type="file" onChange={handleFileChange} />
    </div>
  )
}

サーバ起動

$ node server.js

サーバ側動作確認

ここにアクセスする。 http://localhost:4000/

表示されたPlaygrondで以下のように入力して実行すると Hello World と表示されます。

query {
  hello
}

spollo_file_upload_server

では次はファイルアップロードの確認

フロント側起動

$ npm run start

アクセスする。 http://localhost:3000/

ファイルを選択してアップロードするとこうなる

apollo_file_upload_clinet_image

サーバ側のpublic/images/に選択したファイルが保存される。